﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace DeVierKoeden.Dynamicweb.CreateSite
{
  internal static class Helpers
  {
    /// <summary>
    /// Grants full permissions to the everyone account to the specified file.
    /// </summary>
    /// <param name="file">The file to which the permissions are applied.</param>
    public static void AclFile(string file)
    {
      var sec = File.GetAccessControl(file);
      // Using this instead of the "Everyone" string means we work on non-English systems.
      var everyone = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
      sec.AddAccessRule(new FileSystemAccessRule(everyone, FileSystemRights.Modify | FileSystemRights.Synchronize, InheritanceFlags.None, PropagationFlags.None, AccessControlType.Allow));
      File.SetAccessControl(file, sec);
    }

    /// <summary>
    /// Grants the specified permissions to the specified account for the specified folder.
    /// </summary>
    /// <param name="folder">The folder to which the permissions are applied.</param>
    /// <param name="rights">The permissions to grant on the specified folder.</param>
    /// <param name="account">The account name to apply the permissions to.</param>
    public static void AclFolder(string folder, FileSystemRights rights, string account)
    {
      if (string.IsNullOrEmpty(folder))
      {
        throw new ArgumentException("folder is null or empty.", nameof(folder));
      }
      if (string.IsNullOrEmpty(account))
      {
        throw new ArgumentException("account is null or empty.", nameof(account));
      }
      FileSystemAccessRule accessRule;
      if (account.ToLower() == "everyone")
      {
        var everyone = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
        accessRule = new FileSystemAccessRule(everyone, rights | FileSystemRights.Synchronize, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow);
      }
      else
      {
        accessRule = new FileSystemAccessRule(account, rights | FileSystemRights.Synchronize, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow);
      }
      var sec = Directory.GetAccessControl(folder);
      sec.AddAccessRule(accessRule);
      Directory.SetAccessControl(folder, sec);
    }

    /// <summary>
    /// A helper method to execute a process with command line arguments.
    /// </summary>
    /// <param name="command">The command to execute.</param>
    /// <param name="arguments">The command line parameters to send to the command.</param>
    internal static void RunProcess(string command, string arguments)
    {
      using (var myProcess = new Process())
      {
        myProcess.StartInfo.FileName = command;
        myProcess.StartInfo.Arguments = arguments;
        myProcess.Start();
        myProcess.WaitForExit();
        myProcess.Close();
      }
    }

    /// <summary>
    /// Makes files in a folder structure writable.
    /// </summary>
    /// <param name="folder">The folder to make writeable. This function recurses into all its subfolders.</param>
    internal static void MakeWritable(string folder)
    {
      DateTime now = DateTime.Now;
      try
      {
        foreach (string file in Directory.GetFiles(folder))
        {
          if ((File.GetAttributes(file) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
          {
            File.SetAttributes(file, FileAttributes.Normal);
          }
          File.SetLastWriteTime(file, now);
        }
        foreach (string subFolder in Directory.GetDirectories(folder))
        {
          MakeWritable(subFolder);
        }
      }
      catch (UnauthorizedAccessException)
      {
        // Ignore
      }
    }

    // Taken from http://www.codeproject.com/Tips/512208/Folder-Directory-Deep-Copy-including-sub-directori
    public static void DeepCopy(DirectoryInfo source, DirectoryInfo target, string excludePatternForFolders, bool overwrite)
    {
      // Go through the Directories and recursively call the DeepCopy Method for each one
      foreach (var dir in source.EnumerateDirectories())
      {
        var match = Regex.Match(dir.FullName, excludePatternForFolders, RegexOptions.IgnoreCase);
        if (!match.Success || string.IsNullOrEmpty(excludePatternForFolders))
        {
          DeepCopy(dir, target.CreateSubdirectory(dir.Name), excludePatternForFolders, overwrite);
        }
      }

      // Go ahead and copy each file to the target directory
      foreach (var file in source.EnumerateFiles())
      {
        file.CopyTo(Path.Combine(target.FullName, file.Name), overwrite);
      }
    }

    public static int GetSqlServerDatabaseVersion(string backupPath, ISettings settings)
    {
      int result = -1;
      string sql = $"RESTORE HEADERONLY FROM DISK = '{backupPath}'";
      using (var sqlConnection = new SqlConnection($"data source={settings.SqlServerInstance}; integrated security=true;"))
      {
        using (var sqlCommand = new SqlCommand(sql, sqlConnection))
        {
          try
          {
            sqlConnection.Open();
            using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
            {
              if (sqlDataReader.Read())
              {
                result = Convert.ToInt32(sqlDataReader["DatabaseVersion"].ToString());
              }
              sqlDataReader.Close();
            }
          }
          catch (Exception ex)
          {
            if (ex.HResult == -2146232060)
            {
              MessageBox.Show($"Error retrieving server info for {settings.SqlServerInstance} for site {settings.SiteName}. The backup in {backupPath} may be from an incompatible version of SQL Server. The error message is: {ex.Message}.");
            }
            MessageBox.Show($"Error retrieving server info for {settings.SqlServerInstance} for site {settings.SiteName}. The error message is: {ex.Message}. Canceled site creation.");
          }
        }
      }
      return result;
    }


    public static int GetSqlServerDatabaseVersion(string instanceName, string siteName)
    {
      int result = -1;
      string sql = @"SELECT DatabaseProperty('master','version') AS Version";
      using (var sqlConnection = new SqlConnection($"data source={instanceName}; integrated security=true;"))
      {
        using (var sqlCommand = new SqlCommand(sql, sqlConnection))
        {
          try
          {
            sqlConnection.Open();
            using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
            {
              if (sqlDataReader.Read())
              {
                result = Convert.ToInt32(sqlDataReader["Version"].ToString());
              }
              sqlDataReader.Close();
            }
          }
          catch (Exception ex)
          {
            MessageBox.Show($"Error retrieving server info for {instanceName} for site {siteName} while loading site list.\r\n\r\nThe error message is: {ex.Message}.");
          }
        }
      }
      return result;
    }

    public static string GetSqlServerVersion(string instanceName, string siteName)
    {
      string result = "";
      string sql = @"SELECT @@Version  AS Version";
      using (var sqlConnection = new SqlConnection($"data source={instanceName}; integrated security=true;"))
      {
        using (var sqlCommand = new SqlCommand(sql, sqlConnection))
        {
          sqlConnection.Open();
          try
          {
            using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
            {
              if (sqlDataReader.Read())
              {
                result = sqlDataReader["Version"].ToString();
              }
              sqlDataReader.Close();
            }
          }
          catch (Exception ex)
          {
            MessageBox.Show($"Error retrieving server info for {instanceName} for site {siteName} while loading site list.\r\n\r\nThe error message is: {ex.Message}.");
          }
        }
      }
      return result;
    }


    public static List<TreeNode> GetAllNodes(this TreeView self)
    {
      List<TreeNode> result = new List<TreeNode>();
      foreach (TreeNode child in self.Nodes)
      {
        result.AddRange(child.GetAllNodes());
      }
      return result;
    }

    public static List<TreeNode> GetAllNodes(this TreeNode self)
    {
      List<TreeNode> result = new List<TreeNode> {self};
      foreach (TreeNode child in self.Nodes)
      {
        result.AddRange(child.GetAllNodes());
      }
      return result;
    }
  }
}
